/* ***** BEGIN LICENSE BLOCK *****
 * Version: MPL 1.1/GPL 2.0/LGPL 2.1
 *
 * The contents of this file are subject to the Mozilla Public License Version
 * 1.1 (the "License"); you may not use this file except in compliance with
 * the License. You may obtain a copy of the License at
 * http://www.mozilla.org/MPL/
 *
 * Software distributed under the License is distributed on an "AS IS" basis,
 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
 * for the specific language governing rights and limitations under the
 * License.
 *
 * The Original Code is Qwin extension.
 *
 * The Initial Developer of the Original Code is Ondrej Donek.
 * Portions created by the Initial Developer are Copyright (C) 2008
 * the Initial Developer. All Rights Reserved.
 *
 * Contributor(s):
 *   Ondrej Donek, <ondrejd@gmail.com> (Original Author)
 *
 * Alternatively, the contents of this file may be used under the terms of
 * either the GNU General Public License Version 2 or later (the "GPL"), or
 * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
 * in which case the provisions of the GPL or the LGPL are applicable instead
 * of those above. If you wish to allow use of your version of this file only
 * under the terms of either the GPL or the LGPL, and not to allow others to
 * use your version of this file under the terms of the MPL, indicate your
 * decision by deleting the provisions above and replace them with the notice
 * and other provisions required by the GPL or the LGPL. If you do not delete
 * the provisions above, a recipient may use your version of this file under
 * the terms of any one of the MPL, the GPL or the LGPL.
 *
 * ***** END LICENSE BLOCK ***** */

// Namespace definition
if(typeof(ko) == "undefined") { var ko = {}; }
if(typeof(ko.extensions) == "undefined") { ko.extensions = {}; }
ko.extensions.qwin = {};

/**
 * Qwin JavaScript implementation
 */
(function() {
  // Constants defining modifiers
  const QWIN_MODIFIER_NONE           = "none";
  const QWIN_MODIFIER_ALT            = "alt";
  const QWIN_MODIFIER_CTRL           = "ctrl";
  const QWIN_MODIFIER_SHIFT          = "shift";
  const QWIN_MODIFIER_ALT_CTRL       = "ctrl+alt";
  const QWIN_MODIFIER_ALT_SHIFT      = "alt+shift";
  const QWIN_MODIFIER_CTRL_SHIFT     = "ctrl+shift";
  const QWIN_MODIFIER_CTRL_ALT_SHIFT = "ctrl+alt+shift";

  // Constants defining keysets
  const QWIN_KEYSET_NUMBERS          = "numbers";
  const QWIN_KEYSET_FKEYS            = "fkeys";

  // Constants defining where to open Qwin sidebar
  const QWIN_WHERE_LEFT              = "left";
  const QWIN_WHERE_RIGHT             = "right";

  // Constants defining how to display view names in sidebar's tree
  const QWIN_DISPLAY_FILENAMES_ONLY  = "file-name-only";
  const QWIN_DISPLAY_LAST_FEW_CHARS  = "last-few-chars";
  const QWIN_DISPLAY_FULL_PATHS      = "full-paths";

  // Default count of chars when QWIN_DISPLAY_LAST_FEW_CHARS is used
  const QWIN_DISPLAY_COUNT_OF_CHARS  = 25;

  // Some variables
  var log          = ko.logging.getLogger("ko.extensions.qwin");
  var observerSvc  = Components.classes["@mozilla.org/observer-service;1"].
                     getService(Components.interfaces.nsIObserverService);


  // ========================================================================
  // Preferences

  var prefSrv = Components.classes["@mozilla.org/preferences-service;1"].
      getService(Components.interfaces.nsIPrefService).
      getBranch("extensions.qwin.");

  this.prefs =
  {
    // Getters/Setters
    get enableQwin() { return prefSrv.getBoolPref("enableQwin"); },
    set enableQwin(val) { prefSrv.setBoolPref("enableQwin", val); },

    get usedModifier() { return prefSrv.getCharPref("usedModifier"); },
    set usedModifier(val) { prefSrv.setCharPref("usedModifier", val); },

    get usedKeyset() { return prefSrv.getCharPref("usedKeyset"); },
    set usedKeyset(val) { prefSrv.setCharPref("usedKeyset", val); },

    get displayWhere() { return prefSrv.getCharPref("displayWhere"); },
    set displayWhere(val) { prefSrv.setCharPref("displayWhere", val); },

    get pathsDisplay() { return prefSrv.getCharPref("pathsDisplay"); },
    set pathsDisplay(val) { prefSrv.setCharPref("pathsDisplay", val); },

    get pathsDisplayLength() { return prefSrv.getIntPref("pathsDisplayLength"); },
    set pathsDisplayLength(val) { prefSrv.setIntPref("pathsDisplayLength", val); },

    get highlightCurrent() { return prefSrv.getBoolPref("highlightCurrent"); },
    set highlightCurrent(val) { prefSrv.setBoolPref("highlightCurrent", val); },

    get showIcons() { return prefSrv.getBoolPref("showIcons"); },
    set showIcons(val) { prefSrv.setBoolPref("showIcons", val); },

    get showWebPages() { return prefSrv.getBoolPref("showWebPages"); },
    set showWebPages(val) { prefSrv.setBoolPref("showWebPages", val); },

    get showStartPage() { return prefSrv.getBoolPref("showStartPage"); },
    set showStartPage(val) { prefSrv.setBoolPref("showStartPage", val); },

    get showOnlyActiveProject() { return prefSrv.getBoolPref("showActiveProjectOnly"); },
    set showOnlyActiveProject(val) { prefSrv.setBoolPref("showActiveProjectOnly", val); },

    get showViewsGroups() { return prefSrv.getBoolPref("showViewsGroups"); },
    set showViewsGroups(val) { return prefSrv.setBoolPref("showViewsGroups", val); },

    get highlightUnsaved() { return prefSrv.getBoolPref("highlightUnsaved"); },
    set highlightUnsaved(val) { return prefSrv.setBoolPref("highlightUnsaved"); },

    get highlightUnsavedType() { return prefSrv.getCharPref("highlightUnsavedType"); },
    set highlightUnsavedType(val) { return prefSrv.setCharPref("highlightUnsavedType"); },


    /**
     * Set preferences if not exist (Qwin is running for first time).
     * Called from this.onLoad()
     */
    checkPrefs : function()
    {
      if(!prefSrv.prefHasUserValue("enableQwin"))
        prefSrv.setBoolPref("enableQwin", true);

      if(!prefSrv.prefHasUserValue("usedModifier"))
        prefSrv.setCharPref("usedModifier", QWIN_MODIFIER_ALT_CTRL);

      if(!prefSrv.prefHasUserValue("usedKeyset"))
        prefSrv.setCharPref("usedKeyset", QWIN_KEYSET_FKEYS);

      if(!prefSrv.prefHasUserValue("displayWhere"))
        prefSrv.setCharPref("displayWhere", QWIN_WHERE_LEFT);

      if(!prefSrv.prefHasUserValue("pathsDisplay"))
        prefSrv.setCharPref("pathsDisplay", QWIN_DISPLAY_FILENAMES_ONLY);

      if(!prefSrv.prefHasUserValue("pathsDisplayLength"))
        prefSrv.setIntPref("pathsDisplayLength", QWIN_DISPLAY_COUNT_OF_CHARS);

      if(!prefSrv.prefHasUserValue("highlightCurrent"))
        prefSrv.setBoolPref("highlightCurrent", true);

      if(!prefSrv.prefHasUserValue("showIcons"))
        prefSrv.setBoolPref("showIcons", true);

      if(!prefSrv.prefHasUserValue("showWebPages"))
        prefSrv.setBoolPref("showWebPages", true);

      if(!prefSrv.prefHasUserValue("showStartPage"))
        prefSrv.setBoolPref("showStartPage", true);

      if(!prefSrv.prefHasUserValue("showActiveProjectOnly"))
        prefSrv.setBoolPref("showActiveProjectOnly", false);

      if(!prefSrv.prefHasUserValue("showViewsGroups"))
        prefSrv.setBoolPref("showViewsGroups", true);

      if(!prefSrv.prefHasUserValue("highlightUnsaved"))
        prefSrv.setBoolPref("highlightUnsaved", true);

      if(!prefSrv.prefHasUserValue("highlightUnsavedType"))
        prefSrv.setCharPref("highlightUnsavedType", "asterix");
    } // end checkPrefs()
  };


  // ========================================================================


  /**
   * Prototype object for single Qwin's treeitem
   *
   * @param aIndex {integer}
   * @param aLabel {string}
   * @param aFullPath {string}
   * @param aBaseName {string}
   * @param aIsDirty {boolean}
   * @param aType {string}
   */
  function QwinTreeitemPrototype(aIndex, aLabel, aFullPath, aBaseName,
                                 aIsDirty, aType)
  {
    this.index    = aIndex;
    this.label    = aLabel;
    this.fullPath = aFullPath;
    this.baseName = aBaseName;
    this.isDirty  = aIsDirty;
    this.type     = aType;
  };


  /**
   * Helper prototype object for tree sorting
   *
   * @type Object
   */
  var QwinTreesortHelper = {
    /**
     * Contains name of currently sorted column.
     * @type {string}
     */
    mCol : "",

    /**
     * Holds sort direction: -1 is descending, 0 is natural, 1 is ascending.
     * Meaned as private.
     * @type {integer}
     */
    mDirection : 0,

    /**
     * Returns string representing currently used direction
     * @param {string}
     */
    get mDirectionStr(){ return (this.mDirection == 1) ? "ascending" : "descending"; },

    /**
     * Holds count of tree items to examine if tree's row count is changed from
     * last sort (if we can use simple array reverse).
     * @type {integer}
     */
    mFastIndex : 0,

    /**
     * Method for performing sorting.
     * @param {object}
     * @param {object}
     * @returns {integer}
     */
    sort : function(aX, aY) {
      var isIndex = (this.mCol.indexOf("-tree-position-col") > 0) ? true : false;
      var x = (isIndex) ? parseInt(aX.index) : aX.label;
      var y = (isIndex) ? parseInt(aY.index) : aY.label;

      if(x > y) return 1;
      if(x < y) return -1;

      return 0;
    }
  };


  /**
   * Common tree view prototype object. As a parameter is expected used
   * tree view items.
   *
   * @param aTreeitems {array}
   */
  function QwinTreeviewPrototype(aTreeitems) {
    this.treeitems = aTreeitems;
  };
  QwinTreeviewPrototype.prototype =
  {
    treebox   : null,
    selection : null,

    get rowCount() { return this.treeitems.length; },

    get idPrefix() {
      return "qwin-" + ko.extensions.qwin.prefs.displayWhere + "-tree";
    },

    get atomSrv() {
      return Components.classes["@mozilla.org/atom-service;1"].
          getService(Components.interfaces.nsIAtomService);
    },

    setTree : function(aOut) {
      this.treebox = aOut;
    },

    getCellText : function(aRow, aCol)
    {
      if(aCol.id == this.idPrefix + "-position-col")
        return this.treeitems[aRow].index;

      if(aCol.id == this.idPrefix + "-path-col")
        return " " + this.treeitems[aRow].label;

      return "";
    },

    getCellValue : function(aRow, aCol)
    {
      return this.treeitems[aRow].fullPath;
    },

    getCellProperties : function(aRow, aCol, aProp)
    {
      if(ko.extensions.qwin.prefs.highlightCurrent) {
        var currentView = ko.views.manager.currentView;

        if(currentView.document.displayPath == this.treeitems[aRow].fullPath)
          aProp.AppendElement(this.atomSrv.getAtom("currentView"));
      }

      if(this.treeitems[aRow].isDirty &&
         ko.extensions.qwin.prefs.highlightUnsaved)
      {
        aProp.AppendElement(this.atomSrv.getAtom("isDirty"));
      }
    },

    getImageSrc : function(aRow, aCol)
    {
      var src = "";

      if(ko.extensions.qwin.prefs.showIcons &&
         aCol.id == this.idPrefix + "-path-col")
      {
        switch(this.treeitems[aRow].type)
        {
          case "browser":
            src = "chrome://famfamfamsilk/skin/icons/page_world.png";
            break;

          case "editor":
            src = "chrome://famfamfamsilk/skin/icons/page.png";
            break;

          case "startpage":
            src = "chrome://qwin/skin/openkomodo.png";
            break;
        }
      }

      return src;
    },

    hasNextSibling : function(aRow, aAfterIndex)
    {
      return ((aRow+1)<this.rowCount);
    },

    cycleHeader : function(aCol)
    {
      var tree   = document.getElementById(this.idPrefix);
      var index  = tree.currentIndex;

      if(aCol.id == QwinTreesortHelper.mCol)
        QwinTreesortHelper.mDirection = (QwinTreesortHelper.mDirection == 0)
            ? 1
            : -QwinTreesortHelper.mDirection;

      if(aCol.id == QwinTreesortHelper.mSortedCol &&
         this.rowCount == QwinTreesortHelper.mFastIndex)
      {
        this.treeitems.reverse();
      }
      else
      {
        if(QwinTreesortHelper.mSortedCol !== "") {
          var old = document.getElementById(QwinTreesortHelper.mSortedCol);
          if(old) old.setAttribute("sortDirection", "");
        }

        QwinTreesortHelper.mSortedCol = aCol.id;
        this.treeitems.sort(QwinTreesortHelper.sort);
      }

      aCol.element.setAttribute("sortDirection",
                                QwinTreesortHelper.mDirectionStr);
      this.treebox.invalidate();

      if(index >= 0) {
        this.selection.select(index);
        this.treebox.ensureRowIsVisible(index);
      }

      QwinTreesortHelper.mFastIndex = this.rowCount;
    },

    isSorted : function() {
      return (QwinTreesortHelper.mDirection != 0) ? true : false;
    },

    // Other (mainly unused) methods
    getParentIndex : function(aRow) { return -1; },
    getLevel : function(aRow) { return 0; },
    getColumnProperties : function(aCol, aProp) { return; },
    getRowProperties : function(aRow, aProp) { return; },
    isContainer : function(aRow) { return false; },
    isContainerOpen : function(aRow) { return false; },
    isContainerEmpty : function(aRow) { return; },
    canDrop : function(aRow, aOrientation) { return true; },
    drop : function(aRow, aOrientation) {},
    toggleOpenState : function(aRow) {},
    selectionChanged : function() {},
    cycleCell : function(aRow, aCol) {},
    isEditable : function(aRow, aCol) { return false; },
    setCellText : function(aRow, aCol, aValue) {},
    setCellValue : function(aRow, aCol, aValue) {},
    performAction : function(aAction) {},
    performActionOnRow : function(aAction, aRow) {},
    preformActionOnCell : function(aAction, aRow, aCol) {},
    isSeparator : function(aRow) { return false; }

  }; // End of QwinTreeviewPrototype()


  // ========================================================================
  // Main methods for Qwin


  /**
   * Fired when Qwin is loading
   *
   * @todo Change way how we capturing keypress events!!!
   */
  this.onLoad = function()
  {
    try {
      // We have default values for all values so user doesn't
      // need to go to the Preferences dialog and set up them firstly.
      ko.extensions.qwin.prefs.checkPrefs();

      // Update Qwin's UI
      this.update(ko.extensions.qwin.prefs.displayWhere);

      // Append event observers
      observerSvc.addObserver(this, "open_file", false);
      observerSvc.addObserver(this, "open-url", false);
      observerSvc.addObserver(this, "SciMoz:FileDrop", false);
      observerSvc.addObserver(this, "file_changed", false);

      //observerSvc.addObserver(this, "view_opened", false);
      //observerSvc.addObserver(this, "view_closed", false);
      //observerSvc.addObserver(this, "current_view_changed", false);

      window.addEventListener('current_view_changed', this.update, false);
      window.addEventListener('view_closed', this.update, false);
      window.addEventListener('view_opened', this.update, false);

      // Append preferences observer
      prefSrv.QueryInterface(Components.interfaces.nsIPrefBranch2);
      prefSrv.addObserver("", this, false);

      // Add event listener for capturing keypresses
      // Note: We should definitively use native Komodo's keybindings
      //       and not (Mozilla-like) event listener on events.
      addEventListener("keypress", ko.extensions.qwin.onKeypress, false);

      // Add numbers to tabs
      // we do that always for now
      this.addTabNumberHandler();
    } catch(e) {
      log.exception(e);
    }
  }; // end onLoad()


  /**
   * Correctly unload Qwin (events obervers). Fired when Komodo is going to
   * be closed.
   */
  this.shutdown = function()
  {
    try {
      this.removeTabNumberHandler();

      observerSvc.removeObserver(this, "open_file");
      observerSvc.removeObserver(this, "open-url");
      observerSvc.removeObserver(this, "SciMoz:FileDrop");
      observerSvc.removeObserver(this, "file_changed");

      //observerSvc.removeObserver(this, "view_opened");
      //observerSvc.removeObserver(this, "view_closed");
      //observerSvc.removeObserver(this, "current_view_changed");

      window.removeEventListener('current_view_changed', this.updateEvent, false);
      window.removeEventListener('view_closed', this.updateEvent, false);
      window.removeEventListener('view_opened', this.updateEvent, false);

      prefSrv.QueryInterface(Components.interfaces.nsIPrefBranch2);

      if(prefSrv)
        prefSrv.removeObserver("", this);
    } catch(e) {
      log.exception(e);
    }

    observerSvc = null;
  }; // end shutdown()


  /**
   * Observer imnplementation. We observe changes in views (opening, closing).
   *
   * @param aSubject {string}
   * @param aTopic {string}
   * @param aData {string}
   */
  this.observe = function(aSubject, aTopic, aData)
  {
    try {
      if (!ko.extensions.qwin.prefs.enableQwin) return;

      switch(aTopic) {
        case "open_file":
        case "SciMoz:FileDrop":
        case "view_opened":
        case "view_closed":
        case "current_view_changed":
        case "file_changed":
        case "nsPref:changed":
          this.update();
          break;
      }
    } catch(e) {
      log.exception(e);
    }
  }; // end observe()

  this.updateEvent = function()
  {
    try {
      if (!ko.extensions.qwin.prefs.enableQwin) return;

      this.update();
    } catch(e) {
      log.exception(e);
    }
  };

  /**
   * Add handler patch to display numbers in tabs
   *
   */

  this.addTabNumberHandler = function()
  {
      try {
          var vm = ko.views.manager.topView;
          var box = document.getAnonymousNodes(vm)[0];

          // get the views-tabbed elements
          var tabset1 = box.firstChild;
          var tabset2 = box.lastChild;

          // replace the updateLeafName implementation to use something different
          // for the tab label
          tabset1._qwin_updateLeafName = tabset1.updateLeafName;
          tabset2._qwin_updateLeafName = tabset2.updateLeafName;

          tabset1.updateLeafName =
          tabset2.updateLeafName = function(view) {
              this._qwin_updateLeafName(view);
              try {
                  index = view._qwin_index
                  view.parentNode._tab.label = index+":"+view.parentNode._tab.label;
              } catch(e) {}
          };

          // make sure tab headers are updated
          var views = ko.views.manager.topView.getViews(true);
          for (var i=0; i < views.length; i++) {
              if (views[i].document) {
                  views[i].updateLeafName(views[i]);
              }
          }
      } catch(e) {
          log.exception(e);
      }
  }


  /**
   * Remove handler patch to display numbers in tabs
   *
   */

  this.removeTabNumberHandler = function()
  {
      try {
          var vm = ko.views.manager.topView;
          var box = document.getAnonymousNodes(vm)[0];

          // get the views-tabbed elements
          var tabset1 = box.firstChild;
          var tabset2 = box.lastChild;

          // replace back the updateLeafName implementation
          tabset1.updateLeafName = tabset1._qwin_updateLeafName;
          tabset2.updateLeafName = tabset2._qwin_updateLeafName;
      } catch(e) {
          log.exception(e);
      }
  }


  /**
   * Returns array of paths of opened documents
   *
   * @return {array}
   */
  this.getOpenedDocumentsPaths = function()
  {
    var treeitems = new Array();

    if(ko.extensions.qwin.prefs.showViewsGroups) {
      // XXX Get all views groups and make root treeitems from them
    }

    // XXX Show only views of active project
    var current_project = (ko.extensions.qwin.prefs.showOnlyActiveProject)
        ? ko.projects.active
        : null;

    try {
      var views = ko.views.manager.topView.getDocumentViewList(true);
      var index = 1;

      for(var i=0; i<views.length; i++)
      {
        var view = views[i];


        //freaking nasty solution, but works
        view._qwin_index = index;
        view.updateLeafName(view);



        var viewtype = view.getAttribute("type");

        if(typeof(view.document) == "undefined" || !view.document)
          continue;

        // Check if user want to see Start Page and browsers
        if(!ko.extensions.qwin.prefs.showWebPages && viewtype == "browser") continue;
        if(!ko.extensions.qwin.prefs.showStartPage && viewtype == "startpage") continue;

        // Create new treeitem
        var fullpath = "*** Untitled ***"
        var basename = "*** Untitled ***"
        var label    = "*** Untitled ***";
        var dirty    = true;
        var level    = 0;

        if(!view.document.isUntitled) {
          fullpath = view.document.displayPath;
          basename = view.document.baseName;
          label    = "";
          dirty    = view.document.isDirty;
          level    = 0;

          if(viewtype == "startpage") {
            label = "Start Page";
          } else {
            switch(ko.extensions.qwin.prefs.pathsDisplay) {
              case QWIN_DISPLAY_FILENAMES_ONLY:
                label = basename;
                break;

              case QWIN_DISPLAY_LAST_FEW_CHARS:
                var dl = ko.extensions.qwin.prefs.pathsDisplayLength;
                label = "..." + fullpath.substring(fullpath.length - dl,
                                                   fullpath.length);
                break;

              case QWIN_DISPLAY_FULL_PATHS:
                label = fullpath;
                break;
            }

            if(dirty && ko.extensions.qwin.prefs.higlightUnsaved)
              if(ko.extensions.qwin.prefs.higlightUnsavedType == "asterix")
                label = label + " *";
          }
        }

        treeitems.push(new QwinTreeitemPrototype(index,
                                                 label,
                                                 fullpath,
                                                 basename,
                                                 dirty,
                                                 viewtype,
                                                 level));
        index++;
      }
    } catch(e) {
      log.exception(e);
    }

    return treeitems;
  }; // end getOpenedDocumentsPaths()


  /**
   * Updates Qwin - user preferences and the tree
   */
  this.update = function()
  {
    try {
      var prefix    = ko.extensions.qwin.prefs.displayWhere;
      var tree      = document.getElementById("qwin-" + prefix + "-tree");
      var left_tab  = document.getElementById("qwin_left_tab");
      var right_tab = document.getElementById("qwin_right_tab");

      if(ko.extensions.qwin.prefs.displayWhere == "left") {
        if(left_tab.hasAttribute("collapsed"))
          left_tab.removeAttribute("collapsed");

        right_tab.setAttribute("collapsed", true);
      } else {
        if(right_tab.hasAttribute("collapsed"))
          right_tab.removeAttribute("collapsed");

        left_tab.setAttribute("collapsed", true);
      }

      // Enable/disable Qwin according to user's preferences
      if (ko.extensions.qwin.prefs.enableQwin) {
        tree.disabled = false;
        ko.extensions.qwin.reloadTree();
      } else {
        // When Qwin is disabled we need to ensure that table is empty
        tree.view = new QwinTreeviewPrototype([]);
        tree.disabled = true;
      }
    } catch(e) {
      log.exception(e);
    }
  }; // end update()


  /**
   * Fired whenever we need to reload our tree
   */
  this.reloadTree = function()
  {
    var prefix = ko.extensions.qwin.prefs.displayWhere;
    var tree   = document.getElementById("qwin-" + prefix + "-tree");
    var paths  = this.getOpenedDocumentsPaths();

    tree.view  = new QwinTreeviewPrototype(paths);
  }; // end reloadTree(aPrefix)


  /**
   * Returns currently selected view in tree of active Qwin's sidebar
   */
  this.getCurrentlySelectedView = function()
  {
    try {
      var prefix = ko.extensions.qwin.prefs.displayWhere;
      var tree   = document.getElementById("qwin-" + prefix + "-tree");
      var uri    = tree.view.getCellValue(tree.currentIndex,
                                          tree.columns.getPrimaryColumn());

      return ko.views.manager.getViewForURI(uri);
    } catch(e) {
      log.exception(e);
    }

    return null;
  }; // end getCurrentlySelectedView()


  /**
   * Fired when user doubleclicked on any tree item (document)
   */
  this.onTreeDblClick = function()
  {
    try {
      if(!ko.extensions.qwin.prefs.enableQwin) return;

      var view = this.getCurrentlySelectedView();
      if(!view) return;

      view.makeCurrent(true);
      view.setFocus();
    } catch(e) {
      log.exception(e);
    }
  }; // end onTreeDblClick()


  /**
   * Fired when user press some key
   *
   * @param aEvent {Components.interfaces.nsIDOMKeyEvent}
   */
  this.onKeypress = function(aEvent)
  {
  	try {
      var prefix       = ko.extensions.qwin.prefs.displayWhere;
      var tree         = document.getElementById("qwin-" + prefix + "-tree");
  	  var views_count  = tree.view.rowCount;
      var req_modifier = ko.extensions.qwin.prefs.usedModifier;
      var req_keyset   = ko.extensions.qwin.prefs.usedKeyset;
      var req_view     = -1;

      var needShift = false;
      var needAlt = false;
      var needCtrl = false;

      switch (req_modifier)
      {
        case QWIN_MODIFIER_ALT:
          needAlt=true;
          break;
        case QWIN_MODIFIER_CTRL:
          needCtrl=true;
          break;
        case QWIN_MODIFIER_SHIFT:
          needShift=true;
          break;
        case QWIN_MODIFIER_ALT_CTRL:
          needAlt=true;
          needCtrl=true;
          break;
        case QWIN_MODIFIER_ALT_SHIFT:
          needAlt=true;
          needShift=true;
          break;
        case QWIN_MODIFIER_CTRL_SHIFT:
          needCtrl=true;
          needShift=true;
          break;
        case QWIN_MODIFIER_CTRL_ALT_SHIFT:
          needAlt=true;
          needCtrl=true;
          needShift=true;
          break;
      }

      if ((needAlt == aEvent.altKey) && (needShift == aEvent.shiftKey) && (needCtrl == aEvent.ctrlKey)) {
        if(req_keyset == QWIN_KEYSET_NUMBERS) {
          if(aEvent.charCode >= 48 && aEvent.charCode <= 57 ||
             aEvent.charCode == 45 || aEvent.charCode == 61) {
            switch(aEvent.charCode) {
              case 48: req_view =  9; break; // "0"
              case 45: req_view = 10; break; // "-"
              case 61: req_view = 11; break; // "="
              default:
                if(views_count < (aEvent.charCode - 49)) return;
                req_view = aEvent.charCode - 49;
                break;
            }
          }
        } else if(req_keyset == QWIN_KEYSET_FKEYS) {
          if(aEvent.keyCode >= 112 && aEvent.keyCode <= 123) {
            if(views_count < (aEvent.keyCode - 112)) return;
            req_view = aEvent.keyCode - 112;
          }
        }

        if(req_view != -1) {
          // Switch the view if requested view was found
          tree.currentIndex = req_view;
          ko.extensions.qwin.onTreeDblClick(null);
          aEvent.stopPropagation();
        }
      }
  	} catch(e) {
  	  Components.utils.reportError(e);
  	}
  }; // end onKeypress(aEvent)


  /**
   * Implements other Qwin commands. All commands are related
   * to active Qwin's sidebar (left or right doesn't matter)
   *
   * @param aCmd {String}
   */
  this.doCommand = function(aCmd)
  {
    switch(aCmd)
    {
      // Show currently selected view
      case "showView":
        this.onTreeDblClick();
        break;

      // Close currently selected view
      case "closeView":
        var view = this.getCurrentlySelectedView();
        if(view) view.close();
        break;

      // Close all opened views excluding selected one and Start Page
      case "closeAllOthers":
        this.closeAllOtherViews();
        break;

      // Show Qwin's Preferences dialog
      case "showPrefs":
        var dlg = window.open("chrome://qwin/content/preferences.xul", "",
            "chrome,extrachrome,titlebar,toolbar,modal,centerscreen");
        if(dlg) dlg.focus();
        break;
    }
  }; // end doCommand(aCmd)


  /**
   * Go through the opened views and close all views but currently selected
   * and Start Page not.
   */
  this.closeAllOtherViews = function()
  {
    try {
      var current = ko.views.manager.currentView;
      var views   = ko.views.manager.topView.getDocumentViewList(true);
      var index   = 1;

      for(var i=0; i<views.length; i++)
      {
        var view = views[i];
        var viewtype = view.getAttribute("type");

        if(typeof(view.document) == "undefined" || !view.document ||
           viewtype == "startpage")
          continue;

        if(current.document.displayPath != view.document.displayPath)
          view.close();
      }
    } catch(e) {
      log.exception(e);
    }
  }; // end closeAllOtherViews()

}).apply(ko.extensions.qwin);

// ===========================================================================

// Initialize it once Komodo has finished loading
// XXX: Use an observer or notification mechanism.
window.addEventListener("load",
                 function(event){setTimeout("ko.extensions.qwin.onLoad()", 3000);},
                 false);
window.addEventListener("unload",
                        function(event) { ko.extensions.qwin.shutdown(); }, false);